/*
 * Copyright 2017 Yan Zhenjie.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yanzhenjie.album.app.album.data;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.webkit.URLUtil;

import com.yanzhenjie.album.util.AlbumUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;

/**
 * Created by YanZhenjie on 2017/10/15.
 */
public class ThumbnailBuilder {

    private static final int THUMBNAIL_SIZE = 126 * 2;
    private static final int THUMBNAIL_QUALITY = 100;
    private static final int ORIGINAL_LARGE_SIZE = 5 * 1024;
    private static final int ORIGINAL_SIZE = 2 * 1024;
    private static final int ORIGINAL_SIZE_LARGE = 3000;
    private static final int ORIGINAL_SIZE_MID = 1000;
    private static final int THUMB_SIZE = 180;
    private File mCacheDir;
    private Context context;

    public ThumbnailBuilder(Context context, String packName, String userId) {
        this.context = context;
        this.mCacheDir = AlbumUtils.getAlbumRootThumbFile(context, packName, userId);
        if (mCacheDir.exists() && mCacheDir.isFile()) mCacheDir.delete();
        if (!mCacheDir.exists()) mCacheDir.mkdirs();
    }

    /**
     * Create a thumbnail for the image.
     *
     * @param imagePath image path.
     * @return thumbnail path.
     */
    @WorkerThread
    @Nullable
    public String createThumbnailForImage(String imagePath) {
        if (TextUtils.isEmpty(imagePath)) return null;

        File inFile = new File(imagePath);
        if (!inFile.exists()) return null;

        File thumbnailFile = randomPath(imagePath);
        if (thumbnailFile.exists()) return thumbnailFile.getAbsolutePath();

        Bitmap inBitmap = ratio(imagePath, THUMBNAIL_SIZE, THUMBNAIL_SIZE); //readImageFromPath(imagePath, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
        if (inBitmap == null) return null;

        ByteArrayOutputStream compressStream = new ByteArrayOutputStream();
//        inBitmap.compress(Bitmap.CompressFormat.JPEG, THUMBNAIL_QUALITY, compressStream);
        int quality = 100;
        inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);

        // 循环判断压缩后图片是否超过限制大小
        while (compressStream.toByteArray().length / 1024 > THUMB_SIZE && quality > 1) {
            // 清空baos
            compressStream.reset();
            inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);
            quality -= 1;
        }
        try {
            compressStream.close();
            thumbnailFile.createNewFile();
            FileOutputStream writeStream = new FileOutputStream(thumbnailFile);
            writeStream.write(compressStream.toByteArray());
            writeStream.flush();
            writeStream.close();
            scanFile(context, thumbnailFile.getAbsolutePath());
            return thumbnailFile.getAbsolutePath();
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * Create a thumbnail for the image.
     *
     * @param imagePath image path.
     * @return thumbnail path.
     */
    @WorkerThread
    @Nullable
    public String createThumbnailForImage(String imagePath, String thumbnailPath) {
        if (TextUtils.isEmpty(imagePath)) return null;
        File inFile = new File(imagePath);
        if (!inFile.exists()) return null;
        File thumbnailFile;
        if (TextUtils.isEmpty(thumbnailPath)) {
            thumbnailFile = randomPath(imagePath);
        } else {
            thumbnailFile = new File(thumbnailPath);
        }
        if (thumbnailFile.exists()) return thumbnailFile.getAbsolutePath();

        Bitmap inBitmap = ratio(imagePath, THUMBNAIL_SIZE, THUMBNAIL_SIZE); //readImageFromPath(imagePath, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
        if (inBitmap == null) return null;

        ByteArrayOutputStream compressStream = new ByteArrayOutputStream();
//        inBitmap.compress(Bitmap.CompressFormat.JPEG, THUMBNAIL_QUALITY, compressStream);
        int quality = 100;
        inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);

        // 循环判断压缩后图片是否超过限制大小
        while (compressStream.toByteArray().length / 1024 > THUMB_SIZE && quality > 1) {
            // 清空baos
            compressStream.reset();
            inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);
            quality -= 1;
        }
        try {
            compressStream.close();
            thumbnailFile.createNewFile();

            FileOutputStream writeStream = new FileOutputStream(thumbnailFile);
            writeStream.write(compressStream.toByteArray());
            writeStream.flush();
            writeStream.close();
            scanFile(context, thumbnailFile.getAbsolutePath());
            return thumbnailFile.getAbsolutePath();
        } catch (Exception ignored) {
            return null;
        }
    }

    public String createThumbnailForLimitImageMid(String imagePath) {
        return createThumbnailForLimitImage(imagePath, ORIGINAL_SIZE, ORIGINAL_SIZE_MID);
    }

    public String createThumbnailForLimitImageLarge(String imagePath) {
        return createThumbnailForLimitImage(imagePath, ORIGINAL_LARGE_SIZE, ORIGINAL_SIZE_LARGE);
    }

    /**
     * 控制图片长宽最小值不超过3000
     * Create a thumbnail for the image.
     *
     * @param imagePath image path.
     * @return thumbnail path.
     */
    @WorkerThread
    @Nullable
    public String createThumbnailForLimitImage(String imagePath, int limitSize, int scaleSize) {
        if (TextUtils.isEmpty(imagePath)) return null;

        File inFile = new File(imagePath);
        if (!inFile.exists()) return null;
        String filePath = AlbumUtils.randomJPGPath(inFile.getParentFile());
        File file = new File(filePath);
//        System.out.println("->" + filePath);
        if (file.exists()) return file.getAbsolutePath();
        Bitmap inBitmap = readImageFromPath(imagePath, scaleSize);
        if (inBitmap == null) return null;

        ByteArrayOutputStream compressStream = new ByteArrayOutputStream();
//        inBitmap.compress(Bitmap.CompressFormat.JPEG, THUMBNAIL_QUALITY, compressStream);
        int quality = 100;
        inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);

        // 循环判断压缩后图片是否超过限制大小
        while (compressStream.toByteArray().length / 1024 > limitSize) {
            // 清空baos
            compressStream.reset();
            inBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressStream);
            quality -= 1;
        }
        try {
            compressStream.close();
            if (inFile.exists()) {
                inFile.delete();
                scanFile(context, inFile.getAbsolutePath());
            }
            file.createNewFile();
            FileOutputStream writeStream = new FileOutputStream(file);
            writeStream.write(compressStream.toByteArray());
            writeStream.flush();
            writeStream.close();
            scanFile(context, file.getAbsolutePath());
            return file.getAbsolutePath();
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * 压缩图片
     *
     * @param bitmap    被压缩的图片
     * @param sizeLimit 大小限制
     * @return 压缩后的图片
     */
    private Bitmap compressBitmap(Bitmap bitmap, long sizeLimit) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int quality = 100;
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);

        // 循环判断压缩后图片是否超过限制大小
        while (baos.toByteArray().length / 1024 > sizeLimit) {
            // 清空baos
            baos.reset();
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            quality -= 1;
        }

        Bitmap newBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(baos.toByteArray()), null, null);

        return newBitmap;
    }

    /**
     * 通过像素压缩图像，这将改变图像的宽度/高度。用于获取缩略图
     *
     * @param imgPath image path
     * @param pixelW  目标宽度像素
     * @param pixelH  高度目标像素
     * @return
     */
    public Bitmap ratio(String imgPath, float pixelW, float pixelH) {
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片，此时把options.inJustDecodeBounds 设回true，即只读边不读内容
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Bitmap.Config.RGB_565;
        // 获取位图信息，但请注意位图现在为空
        Bitmap bitmap = BitmapFactory.decodeFile(imgPath, newOpts);

        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 想要缩放的目标尺寸,现在大部分手机都是1080*1920，参考值可以让宽高都缩小一倍
        float hh = pixelH;// 设置高度为960f时，可以明显看到图片缩小了
        float ww = pixelW;// 设置宽度为540f，可以明显看到图片缩小了
        // 缩放比。由于是固定比例缩放，只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if ((w > h && w > ww) || w == h) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if ((w < h && h > hh) || w == h) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 开始压缩图片，注意此时已经把options.inJustDecodeBounds 设回false了
        bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
        // 压缩好比例大小后再进行质量压缩
//      return compressByQuality(bitmap, 100,1000); // 这里再进行质量压缩的意义不大，反而耗资源，删除
        String lowerPath = imgPath.toLowerCase();
        try {
            if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
                int degrees = computeDegree(imgPath);
                if (degrees > 0) {
                    Matrix matrix = new Matrix();
                    matrix.setRotate(degrees);
                    Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                    if (newBitmap != bitmap) {
                        bitmap.recycle();
                        bitmap = newBitmap;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    /**
     * 压缩图像的大小，这将修改图像宽度/高度。用于获取缩略图
     *
     * @param bm
     * @param pixelW target pixel of width
     * @param pixelH target pixel of height
     * @return
     */
    public Bitmap ratio(Bitmap bm, float pixelW, float pixelH) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
        if (os.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片（BitmapFactory.decodeStream）时溢出
            os.reset();// 重置baos即清空baos
            bm.compress(Bitmap.CompressFormat.JPEG, 50, os);// 这里压缩50%，把压缩后的数据存放到baos中
        }
        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 开始读入图片，此时把options.inJustDecodeBounds 设回true了
        newOpts.inJustDecodeBounds = true;
        newOpts.inPreferredConfig = Bitmap.Config.RGB_565;
        Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 想要缩放的目标尺寸,现在大部分手机都是1080*1920，参考值可以让宽高都缩小一倍
        float hh = pixelH;// 设置高度为960f时，可以明显看到图片缩小了
        float ww = pixelW;// 设置宽度为540f，可以明显看到图片缩小了
        // 缩放比。由于是固定比例缩放，只用高或者宽其中一个数据进行计算即可
        int be = 1;// be=1表示不缩放
        if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 设置缩放比例
        // 重新读入图片，注意此时已经把options.inJustDecodeBounds 设回false了
        is = new ByteArrayInputStream(os.toByteArray());
        bitmap = BitmapFactory.decodeStream(is, null, newOpts);
        // 压缩好比例大小后再进行质量压缩
        // return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大，反而耗资源，删除
        return bitmap;
    }

    /**
     * Create a thumbnail for the video.
     *
     * @param videoPath video path.
     * @return thumbnail path.
     */
    @WorkerThread
    @Nullable
    public String createThumbnailForVideo(String videoPath) {
        if (TextUtils.isEmpty(videoPath)) return null;
        File thumbnailFile = randomPath(videoPath);
        if (thumbnailFile.exists()) return thumbnailFile.getAbsolutePath();
        try {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            if (URLUtil.isNetworkUrl(videoPath)) {
                retriever.setDataSource(videoPath, new HashMap<String, String>());
            } else {
                retriever.setDataSource(videoPath);
            }
            Bitmap bitmap = retriever.getFrameAtTime();
            thumbnailFile.createNewFile();
            bitmap.compress(Bitmap.CompressFormat.JPEG, THUMBNAIL_QUALITY, new FileOutputStream(thumbnailFile));
            String newThumbPath = createThumbnailForImage(thumbnailFile.getAbsolutePath());
            if (TextUtils.isEmpty(newThumbPath)) {
                return thumbnailFile.getAbsolutePath();
            }
            thumbnailFile.delete();
            return newThumbPath;
        } catch (Exception ignored) {
            return null;
        }
    }

    private File randomPath(String filePath) {
        String outFilePath = AlbumUtils.getMD5ForString(filePath) + ".album";
        return new File(mCacheDir, outFilePath);
    }

    /**
     * Deposit in the province read images, mViewWidth is high, the greater the picture clearer, but also the memory.
     *
     * @param imagePath pictures in the path of the memory card.
     * @return bitmap.
     */
    @Nullable
    public static Bitmap readImageFromPath(String imagePath, int width, int height) {
        File imageFile = new File(imagePath);
        if (imageFile.exists()) {
            try {
                BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(imageFile));
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(inputStream, null, options);
                inputStream.close();

                options.inJustDecodeBounds = false;
                options.inSampleSize = computeSampleSize(options, width, height);

                Bitmap sampledBitmap = null;
                boolean attemptSuccess = false;
                while (!attemptSuccess) {
                    inputStream = new BufferedInputStream(new FileInputStream(imageFile));
                    try {
                        sampledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
                        attemptSuccess = true;
                    } catch (Exception e) {
                        options.inSampleSize *= 2;
                    }
                    inputStream.close();
                }

                String lowerPath = imagePath.toLowerCase();
                try {
                    if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
                        int degrees = computeDegree(imagePath);
                        if (degrees > 0) {
                            Matrix matrix = new Matrix();
                            matrix.setRotate(degrees);
                            Bitmap newBitmap = Bitmap.createBitmap(sampledBitmap, 0, 0, sampledBitmap.getWidth(), sampledBitmap.getHeight(), matrix, true);
                            if (newBitmap != sampledBitmap) {
                                sampledBitmap.recycle();
                                sampledBitmap = newBitmap;
                            }
                        }
                    }
                } catch (Exception e) {
                }
                return sampledBitmap;
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    private static int computeSampleSize(BitmapFactory.Options options, int width, int height) {
        int inSampleSize = 1;
//        if (options.outWidth > width || options.outHeight > height) {
//            int widthRatio = Math.round((float) options.outWidth / (float) width);
//            int heightRatio = Math.round((float) options.outHeight / (float) height);
//            inSampleSize = Math.min(widthRatio, heightRatio);
//        }
        int w = options.outWidth;
        int h = options.outHeight;
        // 想要缩放的目标尺寸,现在大部分手机都是1080*1920，参考值可以让宽高都缩小一倍
//        float ww;// 设置宽度为540f，可以明显看到图片缩小了
//        if ((h == 3952 && w == 1824) || (h == 1824 && w == 3952)) {
//            ww = 126;
//        } else if ((w * h) % (16 * 9) == 0 && w != h) {
//            ww = 126;
//        } else if ((w * h) % (4 * 3) == 0 && w != h) {
//            ww = 168;
//        } else {
//            ww = 144;
//        }
        if (w > h && h > THUMBNAIL_SIZE) {// 如果宽度大的话根据宽度固定大小缩放
            inSampleSize = (int) (options.outWidth / THUMBNAIL_SIZE);
        } else if (w < h && w > THUMBNAIL_SIZE) {// 如果高度高的话根据宽度固定大小缩放
            inSampleSize = (int) (options.outHeight / THUMBNAIL_SIZE);
        } else if (w == h && w >= THUMBNAIL_SIZE) {// 如果高度:宽度 1:1
            inSampleSize = (int) (options.outHeight / THUMBNAIL_SIZE);
        }
        if (inSampleSize <= 0)
            inSampleSize = 1;
        return inSampleSize;
    }

    @Nullable
    private static Bitmap readImageFromPath(String imagePath, int scaleSize) {
        File imageFile = new File(imagePath);
        if (imageFile.exists()) {
            try {
                BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(imageFile));
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(inputStream, null, options);
                inputStream.close();

                options.inJustDecodeBounds = false;
                int minSize = Math.min(options.outWidth, options.outHeight);
                options.inSampleSize = minSize < scaleSize ? 1 : minSize / scaleSize;

                Bitmap sampledBitmap = null;
                boolean attemptSuccess = false;
                while (!attemptSuccess) {
                    inputStream = new BufferedInputStream(new FileInputStream(imageFile));
                    try {
                        sampledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
                        attemptSuccess = true;
                    } catch (Exception e) {
                        options.inSampleSize *= 2;
                    }
                    inputStream.close();
                }

                String lowerPath = imagePath.toLowerCase();
                try {
                    if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
                        int degrees = computeDegree(imagePath);
                        if (degrees > 0) {
                            Matrix matrix = new Matrix();
                            matrix.setRotate(degrees);
                            Bitmap newBitmap = Bitmap.createBitmap(sampledBitmap, 0, 0, sampledBitmap.getWidth(), sampledBitmap.getHeight(), matrix, true);
                            if (newBitmap != sampledBitmap) {
                                sampledBitmap.recycle();
                                sampledBitmap = newBitmap;
                            }
                        }
                    }
                } catch (Exception e) {
                }
                return sampledBitmap;
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    private static int computeDegree(String path) {
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90: {
                    return 90;
                }
                case ExifInterface.ORIENTATION_ROTATE_180: {
                    return 180;
                }
                case ExifInterface.ORIENTATION_ROTATE_270: {
                    return 270;
                }
                default: {
                    return 0;
                }
            }
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 通知android媒体库更新文件夹
     *
     * @param filePath ilePath 文件绝对路径，、/sda/aaa/jjj.jpg
     */
    public void scanFile(Context context, String filePath) {
        try {
            File file = new File(filePath);
            MediaScannerConnection.scanFile(context, new String[]{file.getParentFile().getAbsolutePath(),filePath}, null,
                    new MediaScannerConnection.OnScanCompletedListener() {
                        public void onScanCompleted(String path, Uri uri) {
                        }
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}